﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.Reflection;

namespace Conteinerid
{
    public static class UtilCodeDomExtesions
    {
        public static MemberAttributes GetAttributes(this MethodInfo metodo)
        {
            MemberAttributes memberAttributes = 0;
            if (metodo.IsVirtual)
            {
                memberAttributes |= MemberAttributes.Override;
            }

            if (metodo.IsFamilyAndAssembly)
            {
                memberAttributes |= MemberAttributes.FamilyAndAssembly;
            }

            if (metodo.IsFamilyOrAssembly)
            {
                memberAttributes |= MemberAttributes.FamilyOrAssembly;
            }

            if (metodo.IsAssembly)
            {
                memberAttributes |= MemberAttributes.Assembly;
            }

            if (metodo.IsPublic)
            {
                memberAttributes |= MemberAttributes.Public;
            }

            if (metodo.IsFamily)
            {
                memberAttributes |= MemberAttributes.Family;
            }

            return memberAttributes;
        }

        public static CodeTypeReference GetCodeTypeReference<Tipo>()
        {
            return GetCodeTypeReference(typeof(Tipo));
        }

        public static CodeTypeReference GetCodeTypeReference(this Type tipo)
        {
            CodeTypeReference baseType;
            baseType = new CodeTypeReference(GetDescricaoTipo(tipo));
            foreach (var item in tipo.GetGenericArguments())
            {
                var codeTypeRef = GetCodeTypeReference(item);
                baseType.TypeArguments.Add(codeTypeRef);
            }
            return baseType;
        }

        public static string GetDescricaoTipo(this Type tipo)
        {
            return tipo.Name.Substring(0, tipo.Name.IndexOf("`") == -1 ? tipo.Name.Length : tipo.Name.IndexOf("`"));
        }

        public static bool PodeSerInstanciado(this Type type)
        {
            if (type==null)
            {
                throw new NullReferenceException("Tipo informado é nulo!");
            }

            if (type.IsAbstract)
            {
                return false;
            }
            
            if (type.IsInterface)
            {
                return false;
            }

            return true;
        }
    }

    public static class CodeTypeDeclarationExtensions
    {
        public static CodeFieldReferenceExpression GetReference(this CodeMemberField codeMemberField, CodeExpression target)
        {
            return new CodeFieldReferenceExpression(target,codeMemberField.Name);
        }

        public static CodeMemberField AddField<FieldType>(this CodeTypeDeclaration declaringType, string name, MemberAttributes escopo, Type concreteInstanceType)
        {
            return declaringType.AddField(typeof(FieldType), name, escopo, concreteInstanceType);
        }

        public static CodeMemberField AddField(this CodeTypeDeclaration declaringType, Type type, string name, MemberAttributes escopo, Type concreteInstanceType)
        {
            if (type == null)
	        {
		         throw new ArgumentException("Tipo do campo não pode ser nulo.");
	        }

            if (!concreteInstanceType.PodeSerInstanciado())
            {
                throw new ArgumentException("O tipo informado não pode ser instanciado.");
            }

            if (!concreteInstanceType.IsSubclassOf(type))
            {
                throw new ArgumentException("Os tipos declarado e concreto não são correlatos.");
            }

            if (declaringType == null)
            {
                throw new ArgumentException("O tipo declarado é nulo. Não é possível adicionar um campo.");
            }

            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("O nome do campo não pode ser nulo.");
            }
            
            CodeMemberField field = new CodeMemberField()
            {
                Attributes = escopo,
                Name = name,
                Type = type.GetCodeTypeReference(),
            };
            if (concreteInstanceType != null)
	        {
                field.InitExpression = new CodeObjectCreateExpression()
                                        {
                                            CreateType = concreteInstanceType.GetCodeTypeReference()
                                        };
	        }
            declaringType.Members.Add(field);
            return field;
        }

        public static CodeMemberMethod AddMethod(this CodeTypeDeclaration declaringType, string name, Type returnType, CodeParameterDeclarationExpression[] parameters)
        {
            CodeMemberMethod m = new CodeMemberMethod();

            m.Name = name;
            if (returnType != null)
            {
                m.ReturnType = returnType.GetCodeTypeReference();
            }

            foreach (var item in parameters)
            {
                //if (item.Type.IsGenericType)
                //{
                    m.TypeParameters.Add(item.Name);    
                //}
                    m.Parameters.Add(item);
            }
            

            declaringType.Members.Add(m);
            return m;
        }

        public static CodeMemberMethod InvokeMethod(this CodeMemberMethod codeMemberMethod)
        {
            return codeMemberMethod;
        }

    }
}
